﻿using CommunityToolkit.Mvvm.Input;
using ImageHandler.Models;
using Microsoft.Win32;
using OpenCvSharp;
using System.IO;
using System.Windows.Media.Imaging;

namespace ImageHandler.ViewModels
{
    public class E001_ObjectNumberRecognitionViewModel : PartViewModel
    {
        public E001_ObjectNumberRecognitionViewModel()
        {
            Header = "目标数量识别";
        }

        public override void Run(string path)
        {
            // 读取图像
            Mat src = Cv2.ImRead(path, ImreadModes.Color);
            if (src.Empty())
            {
                OperateLogAction?.Invoke("无法读取图像！");
                return;
            }

            Result.ReslutInfos.Clear();
            // 获取目标数量
            //图像灰度
            Mat matGray = new Mat();
            Cv2.CvtColor(src, matGray, ColorConversionCodes.BGR2GRAY);
            AddMatToResultInfos(matGray, "图像灰度");

            //图像滤波
            Mat blurMat = new Mat();
            Cv2.MedianBlur(matGray, blurMat, 5);
            AddMatToResultInfos(matGray, "图像滤波-MedianBlur");

            //图像二值化
            // 1. 二值化是将图像中每个像素的灰度值与一个阈值进行比较
            // 2. 如果像素的灰度值大于阈值，则将其设置为255（白色），否则设置为0（黑色）
            // 3. thresh (double)：阈值，图像中的像素值与此阈值进行比较。
            // 4. maxval (double)：最大值，表示当像素值大于阈值时设置的值。
            // 5. ThresholdTypes.Binary: 二值化类型，表示将像素值大于阈值的像素设置为255，小于阈值的像素设置为0
            // 6. ThresholdTypes.Otsu: 自适应阈值化方法，自动计算最佳阈值
            Mat binaryMat = new Mat();
            Cv2.Threshold(blurMat, binaryMat, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
            AddMatToResultInfos(binaryMat, "图像二值化-Threshold");

            //生成不同形状和大小的结构元素, 结构元素的作用是与图像进行形态学操作时确定操作的区域和形态。
            // 1. MorphShapes.Rect: 矩形结构元素
            // 2. ksize : 结构元素的大小，指定结构元素的宽度和高度
            // 3. anchor : 结构元素的锚点，默认为 (-1, -1)。这个点用于确定结构元素的对齐方式，通常会位于结构元素的中心。锚点决定了图像处理时结构元素的对齐位置。
            Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 3), new Point(-1, -1));

            //形态学操作 , MorphologyEx - 应用开运算
            // 1. src: 输入图像，必须是二值图像
            // 2. dst: 输出图像，和输入图像大小相同
            // 3. op: 形态学操作类型，MorphTypes.Open 表示开运算
            // 4. kernel: 结构元素，指定形态学操作的形状和大小
            // 5. anchor: 锚点，指定结构元素的中心点
            // 6. iterations: 迭代次数，指定形态学操作的重复次数
            Cv2.MorphologyEx(binaryMat, binaryMat, MorphTypes.Dilate, kernel, new Point(-1, -1), 1);
            AddMatToResultInfos(binaryMat, "图像腐蚀-MorphologyEx");

            // 距离变换：用于二值化图像中的每一个非零点距自己最近的零点的距离，距离变换图像上越亮的点，代表了这一点距离零点的距离越远
            Mat dist = new Mat();
            Cv2.BitwiseNot(binaryMat, binaryMat);

            //距离变换是将二值图像中的每个像素替换为其到最近的非零像素的距离
            //函数的主要功能是计算每个像素点到最近的边界的距离。
            //对于一个二值图像，它将计算每个背景像素（值为0）到前景区域（值为255）的最近距离，这样可以得到一个距离变换图像。
            // 1. src: 输入图像，必须是二值图像
            // 2. dst: 输出图像，和输入图像大小相同
            // 3. distanceType: 距离类型，DistanceTypes.L2 表示欧几里得距离, DistanceTypes.L1 表示曼哈顿距离, DistanceTypes.C 表示棋盘距离
            // 4. maskSize: 掩膜大小，指定计算距离时使用的邻域大小
            Cv2.DistanceTransform(binaryMat, dist, DistanceTypes.L2, DistanceTransformMasks.Mask3);

            //归一化距离变换图像，将像素值缩放到指定范围
            // 1. src: 输入图像
            // 2. dst: 输出图像，和输入图像大小相同
            // 3. alpha: 输出图像的最小值
            // 4. beta: 输出图像的最大值
            // 5. normType: 归一化类型，NormTypes.MinMax 表示将像素值缩放到指定范围
            // 6. mask: 掩膜，指定要归一化的区域
            // 7. dtype: 输出图像的数据类型, -1表示与输入图像相同, CV_8U表示8位无符号整数 , CV_32F表示32位浮点数, CV_64F表示64位浮点数
            Cv2.Normalize(dist, dist, 0, 1, NormTypes.MinMax);
            AddMatToResultInfos(dist, "距离变换-DistanceTransform");

            //阈值化二值分割
            Mat dist_8U = new Mat();
            dist.ConvertTo(dist_8U, MatType.CV_8U);

            //主要用于图像二值化处理。与全局阈值化不同，自适应阈值化可以根据图像局部区域的亮度信息动态选择不同的阈值，适用于照明不均或对比度低的图像。
            // 1. src: 输入图像，必须是灰度图像
            // 2. dst: 输出图像，和输入图像大小相同
            // 3. maxValue: 最大值，表示当像素值大于阈值时设置的值
            // 4. adaptiveMethod: 自适应方法，MeanC 根据邻域区域的均值来计算阈值，GaussianC 根据邻域区域的加权均值来计算阈值，使用高斯加权
            // 5. thresholdType: 阈值类型，Binary 表示二值化，BinaryInv 表示反向二值化
            // 6. blockSize: 邻域大小，必须是奇数，表示计算阈值时使用的邻域区域大小。
            //     较大的 blockSize 可以处理光照不均问题，但可能会导致图像细节丢失；较小的 blockSize 则能保留更多细节，但对光照变化的鲁棒性较差
            // 7. C: 常数，表示从计算的阈值中减去的值，用于调整阈值
            Cv2.AdaptiveThreshold(dist_8U, dist_8U, 250, AdaptiveThresholdTypes.GaussianC, ThresholdTypes.Binary, 101, 0);
            AddMatToResultInfos(dist_8U, "阈值化二值分割-AdaptiveThreshold");

            kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(5, 3), new Point(-1, -1));
            Cv2.Dilate(dist_8U, dist_8U, kernel, new Point(-1, -1), 2);
            AddMatToResultInfos(dist_8U, "阈值化二值分割-AdaptiveThreshold-Dilate");

            //轮廓检测, FindContours - 检测轮廓,
            // 1. 输入图像必须是二值图像,格式必须是8位的单通道图像
            // 2. contours, 检测到的轮廓集合，类型为 Point[][]。每个轮廓由若干个 Point 组成的数组表示。
            // 3. hierarchy, 轮廓的层次结构，类型为 HierarchyIndex[]。每个轮廓 contours[i] 对应于层次结构 hierarchy[i]。
            //     Parent: 父轮廓索引（无父则为-1）
            //     Next: 同一层级的下一个轮廓索引
            //     Previous: 同一层级的上一个轮廓索引
            //     FirstChild: 第一个子轮廓索引
            // 4. mode, 轮廓检索模式.External: 只检测外轮廓, List: 检测所有轮廓, CComp: 检测所有轮廓并将其组织为两层的层次结构, Tree: 检测所有轮廓并重构嵌套轮廓的整个层次结构
            // 5. method, 轮廓近似方法. ApproxNone: 不近似(保存所有轮廓点), ApproxSimple: 近似方法(压缩冗余点（如矩形保留4个顶点）), ApproxTC89L1: 近似方法(使用 Teh-Chin 链逼近算法)
            // 6. offset, 轮廓点偏移量

            // 注意事项:
            // 1：输入图像必须为二值化的单通道图像，否则结果不可靠。未预处理可能导致检测失败。
            // 2：当处理嵌套轮廓（如孔洞）时，RetrievalModes.Tree 或 CComp 配合 hierarchy 可精确管理轮廓关系。
            // 3：轮廓检测结果可能受到图像质量、阈值设置、轮廓近似方法等因素影响。
            // 4:   ApproxSimple 可显著减少轮廓点数，提升后续处理速度

            Point[][] contours;
            HierarchyIndex[] hierarchies;
            Cv2.FindContours(dist_8U, out contours, out hierarchies, RetrievalModes.External, ContourApproximationModes.ApproxSimple);

            //绘制结果图像
            Mat markRet = Mat.Zeros(src.Size(), MatType.CV_8UC3);
            RNG rng = new RNG(12345);
            for (int i = 0; i < contours.Length; i++)
            {
                Cv2.DrawContours(markRet, contours, i, new Scalar(rng.Uniform(0, 255), rng.Uniform(0, 255), rng.Uniform(0, 255)), -1, LineTypes.Link8);
            }
            AddMatToResultInfos(markRet, "绘制结果图像-markRet");

            Result.Result = contours.Length + "";
        }
    }
}
